#define CHECK_NumberVec4

using System;

namespace FixedPointMath
{
    [Serializable]
    public struct Vector4
    {
        public Number x;

        public Number y;

        public Number z;

        public Number w;

        public static Vector4 One = new Vector4 { x = Number.N1, y = Number.N1, z = Number.N1, w = Number.N1 };
        public static Vector4 Zero = new Vector4 { x = Number.Zero, y = Number.Zero, z = Number.Zero, w = Number.Zero };

        public Vector4(Number _x, Number _y)
        {
            x = _x;
            y = _y;
            z = Number.Zero;
            w = Number.Zero;
        }
        public Vector4(Number _x, Number _y, Number _z)
        {
            x = _x;
            y = _y;
            z = _z;
            w = Number.Zero;
        }
        public Vector4(Number _x, Number _y, Number _z, Number _w)
        {
            this.x = _x;
            this.y = _y;
            this.z = _z;
            this.w = _w;
        }

        /// <summary>
        /// 平方根
        /// </summary>
        public Number magnitude
        {
            get
            {
                return sqrMagnitude.Sqrt();
            }
        }
        public Vector4 normalized
        {
            get
            {
                return Vector4.Normalize(this);
            }
        }
        /// <summary>
        /// 根的平方
        /// </summary>
        public Number sqrMagnitude
        {
            get
            {
                return x * x + y * y + z * z + w * w;
            }
        }

        #region function
        public Number this[int index]
        {
            get
            {
                switch (index)
                {
                    case 0:
                        return x;
                    case 1:
                        return y;
                    case 2:
                        return z;
                    case 3:
                        return w;
                    default:
                        throw new IndexOutOfRangeException("Invalid NumberVec4 get index!");
                }
            }
            set
            {
                switch (index)
                {
                    case 0:
                        x = value;
                        break;
                    case 1:
                        y = value;
                        break;
                    case 2:
                        z = value;
                        break;
                    case 3:
                        w = value;
                        break;
                    default:
                        throw new IndexOutOfRangeException("Invalid NumberVec4 set index!");
                }
            }
        }

        /// <summary>
        /// 距离
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static Number Distance(Vector4 a, Vector4 b)
        {
            return MathFP.Sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y) + (a.z - b.z) * (a.z - b.z) + (a.w - b.w) * (a.w - b.w));
        }
        /// <summary>
        /// 点乘
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        /// <returns></returns>
        public static Number Dot(Vector4 lhs, Vector4 rhs)
        {
            return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z + lhs.w * rhs.w;
        }
        /// <summary>
        /// 线性插值
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="t"></param>
        /// <returns></returns>
        public static Vector4 Lerp(Vector4 a, Vector4 b, Number t)
        {
            if (t <= Number.Zero)
            {
                return a;
            }
            else if (t >= Number.N1)
            {
                return b;
            }
            return a + (b - a) * t;
        }
        /// <summary>
        /// 线性插值(无限制)
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="t"></param>
        /// <returns></returns>
        public static Vector4 LerpUnclamped(Vector4 a, Vector4 b, Number t)
        {
            return a + (b - a) * t;
        }
        /// <summary>
        /// 向量模长
        /// </summary>
        /// <param name="a"></param>
        /// <returns></returns>
        public static Number Magnitude(Vector4 a)
        {
            return a.magnitude;
        }

        /// <summary>
        /// 最大值(X,Y,Z,W均取最大)
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        /// <returns></returns>
        public static Vector4 Max(Vector4 lhs, Vector4 rhs)
        {
            Vector4 temp = new Vector4();
            temp.x = MathFP.Max(lhs.x, rhs.x);
            temp.y = MathFP.Max(lhs.y, rhs.y);
            temp.z = MathFP.Max(lhs.z, rhs.z);
            temp.w = MathFP.Max(lhs.w, rhs.w);
            return temp;
        }
        /// <summary>
        /// 最小值(X,Y,Z,W均取最小)
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        /// <returns></returns>
        public static Vector4 Min(Vector4 lhs, Vector4 rhs)
        {
            Vector4 temp = new Vector4();
            temp.x = MathFP.Min(lhs.x, rhs.x);
            temp.y = MathFP.Min(lhs.y, rhs.y);
            temp.z = MathFP.Min(lhs.z, rhs.z);
            temp.w = MathFP.Min(lhs.w, rhs.w);
            return temp;
        }
        /// <summary>
        /// 向目标点移动
        /// </summary>
        /// <param name="current"></param>
        /// <param name="target"></param>
        /// <param name="maxDistanceDelta"></param>
        /// <returns></returns>
        public static Vector4 MoveTowards(Vector4 current, Vector4 target, Number maxDistanceDelta)
        {
            Vector4 vector4 = target - current;
            Number single = vector4.magnitude;
            if (single <= maxDistanceDelta || single == Number.Zero)
            {
                return target;
            }
            return current + ((vector4 / single) * maxDistanceDelta);
        }
        /// <summary>
        /// 单位化
        /// </summary>
        /// <param name="a"></param>
        /// <returns></returns>
        public static Vector4 Normalize(Vector4 value)
        {
            if (value == Zero)
            {
                return Zero;
            }
            else
            {
                Vector4 tempDVec = new Vector4();
                tempDVec.x = value.x / value.magnitude;
                tempDVec.y = value.y / value.magnitude;
                tempDVec.z = value.z / value.magnitude;
                tempDVec.w = value.w / value.magnitude;
                return tempDVec;
            }
        }
        /// <summary>
        /// 向量投影
        /// </summary>
        /// <param name="vector"></param>
        /// <param name="onNormal"></param>
        /// <returns></returns>
        public static Vector4 Project(Vector4 vector, Vector4 onNormal)
        {
            if (vector == Zero || onNormal == Zero)
            {
                return Zero;
            }
            return Dot(vector, onNormal) / (onNormal.magnitude * onNormal.magnitude) * onNormal;
        }
        /// <summary>
        /// 向量缩放
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static Vector4 Scale(Vector4 a, Vector4 b)
        {
            Vector4 temp = new Vector4();
            temp.x = a.x * b.x;
            temp.y = a.y * b.y;
            temp.z = a.z * b.z;
            temp.w = a.w * b.w;
            return temp;
        }
        /// <summary>
        /// 模长平方
        /// </summary>
        /// <param name="a"></param>
        /// <returns></returns>
        public static Number SqrMagnitude(Vector4 a)
        {
            return a.sqrMagnitude;
        }
        /// <summary>
        /// 单位化
        /// </summary>
        public void Normalize()
        {
            if (this != Zero)
            {
                Number length = magnitude;
                x /= length;
                y /= length;
                z /= length;
                w /= length;
            }
        }
        /// <summary>
        /// 缩放
        /// </summary>
        /// <param name="scale"></param>
        public void Scale(Vector4 scale)
        {
            x *= scale.x;
            y *= scale.y;
            z *= scale.z;
            w *= scale.w;
        }
        /// <summary>
        /// 设置向量
        /// </summary>
        /// <param name="_x"></param>
        /// <param name="_y"></param>
        /// <param name="_z"></param>
        /// <param name="_w"></param>
        public void Set(Number _x, Number _y, Number _z, Number _w)
        {
            x = _x;
            y = _y;
            z = _z;
            w = _w;
        }
        /// <summary>
        /// 模长平方
        /// </summary>
        /// <returns></returns>
        public Number SqrMagnitude()
        {
            return sqrMagnitude;
        }
        #endregion

        #region ovveride
        public override string ToString()
        {
            return String.Format("({0}, {1}, {2}, {3})", x, y, z, w);
        }
        public override int GetHashCode()
        {
            return this.x.GetHashCode() ^ this.y.GetHashCode() << 2 ^ this.z.GetHashCode() >> 2 ^ this.w.GetHashCode() >> 1;
        }
        public override bool Equals(object other)
        {
            if (other is Vector4)
            {
                var oth = (Vector4)other;
                return this == oth;
            }
            return false;
        }
        #endregion

        #region operator
        public static Vector4 operator +(Vector4 a, Vector4 b)
        {
            return new Vector4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w);
        }
        public static Vector4 operator -(Vector4 a)
        {
            return new Vector4(-a.x, -a.y, -a.z, -a.w);
        }
        public static Vector4 operator -(Vector4 a, Vector4 b)
        {
            return new Vector4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w);
        }
        public static Vector4 operator *(Number d, Vector4 a)
        {
            return new Vector4(a.x * d, a.y * d, a.z * d, a.w * d);
        }
        public static Vector4 operator *(Vector4 a, Number d)
        {
            return new Vector4(a.x * d, a.y * d, a.z * d, a.w * d);
        }
        public static Vector4 operator /(Vector4 a, Number d)
        {
            return new Vector4(a.x / d, a.y / d, a.z / d, a.w / d);
        }
        public static bool operator ==(Vector4 lhs, Vector4 rhs)
        {
            return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z && lhs.w == rhs.w;
        }
        public static bool operator !=(Vector4 lhs, Vector4 rhs)
        {
            return !(lhs == rhs);
        }
        #endregion
    }
}
